/**
 * Copyright 2006 gworks.com.au
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. (http://www.apache.org/licenses/LICENSE-2.0)
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for 
 * the specific language governing permissions and limitations under the License.
 *
 * <dhamma-message src="Atisha (11th century Tibetan Buddhist master)">
 *  The greatest achievement is selflessness. The greatest worth is self-mastery.
 *  The greatest quality is seeking to serve others. The greatest precept is continual awareness.
 *  The greatest medicine is the emptiness of everything. The greatest action is not conforming with the worlds ways.
 *  The greatest magic is transmuting the passions. The greatest generosity is non-attachment.
 *  The greatest goodness is a peaceful mind. The greatest patience is humility.
 *  The greatest effort is not concerned with results. The greatest meditation is a mind that lets go.
 *  The greatest wisdom is seeing through appearances. 
 * </dhamma-message>
 * 
 * @author Ashin Wimalajeewa (ash)
 */
package org.javaongems.rzk.client.zul;

import java.util.ArrayList;
import java.util.Iterator;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.WindowResizeListener;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.KeyboardListenerCollection;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;

public class Splitter extends FocusWidget {
	final static private int MOD_KEYS[] = {KeyboardListener.KEY_CTRL, KeyboardListener.KEY_SHIFT, KeyboardListener.KEY_ALT};
	final static public String BEFORE = "before";
	final static public String AFTER = "after";
	final static public String NONE = "none";
	final static private String[] COLL_STYLES = {BEFORE, AFTER, NONE};
	
		static private int V_COLLAPSE_WIDTH = -1;
		static private int H_COLLAPSE_HEIGHT = -1;
	
		static private ArrayList allSplitters = new ArrayList();
		static private WindowResizer windowResizer;
		
	private boolean horizOrientation = true;
	private String collapse = NONE;
	
	protected Element collapseElement;
	protected CollapseHandler collapseHandler = new CollapseHandler();
	
	private Element spltElement, prevSibling, nextSibling;
	private boolean mouseDown;
	private boolean dragging;
	private int dragX, dragY;
	private boolean open = true;
	
	public Splitter() {
		super(DOM.createDiv());
		if (windowResizer == null) {
			windowResizer = new WindowResizer();
			Window.addWindowResizeListener(windowResizer);
		}
		spltElement = getElement();
		sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS);
		setStyleName("zk-splitter");
		setTabIndex(1);
	}
	
	public boolean isOpen() {
		return open;
	}
	
	public void setOpen(boolean o) {
		if (open == o)
			return;
		if (isCollapseNone())
			return;
		open = o;
		showSibling(open);
	}
	
	public String getCollapse() {
		return collapse;
	}
	
	public void setCollapse(String c) {
		if (collapse == c)
			return;
		assertValidCollapseArg(c);
		collapse = c;
		updateCollapseView();
	}
	
	private void assertValidCollapseArg(String c) {
		boolean ok = false;
		for (int i = 0; i < COLL_STYLES.length; i++) {
			if (COLL_STYLES[i].equals(c)) {
				ok = true;
				break;
			}
		}
		assert ok;
		if (!ok) throw new IllegalArgumentException();
	}

	private boolean isCollapseNone() {
		return collapse == null || NONE.equals(collapse);
	}
	
	public void onBrowserEvent(Event event) {
		super.onBrowserEvent(event);
		int evt = DOM.eventGetType(event);
	    switch (evt) {
	      case Event.ONMOUSEUP:
	    	  if (dragging)
	    		  DOM.releaseCapture(spltElement);
	    	  mouseDown = false;
	    	  dragging = false;
	    	  break;
	      case Event.ONMOUSEDOWN:
	    	  mouseDown = true;
	    	  break;
	      case Event.ONMOUSEMOVE:
	    	  if (mouseDown) {
	    		  int x = DOM.eventGetClientX(event);
	    		  int y = DOM.eventGetClientY(event);
	    		  if (!dragging) {
	    			  dragging = true;
		    		  dragX = x; dragY = y;
		    		  DOM.setCapture(spltElement);
		    		  resetSiblings(DOM.getParent(spltElement));
	    		  } else
	    			  dragSplitter(event, x, y);
	    	  }
	    	  break;
	      case Event.ONFOCUS:
	    	  resetSiblings(DOM.getParent(spltElement));
	    	  break;
	      case Event.ONKEYUP:
	    	  int mods = KeyboardListenerCollection.getKeyboardModifiers(event);
	    	  handleKeyUpAction((char)DOM.eventGetKeyCode(event), mods);
	    	  break;
	    }
	}
	
	private void dragSplitter(Event event, int x, int y) {
		int deltaX = x - dragX; int deltaY = y - dragY;
		dragX = x; dragY = y;
		if (deltaX == 0 && deltaY == 0)
			return;
		int sz = horizOrientation ? deltaX: deltaY;
		resizeSiblings(sz);
	}
	
	private void resizeSiblings(int sz) {
		if (sz == 0)
			return;
		String dim = horizOrientation ? "offsetWidth": "offsetHeight";
		String dimStyle = horizOrientation ? "width": "height";
		int prevVal = DOM.getIntAttribute(prevSibling, dim);
		int nextVal = DOM.getIntAttribute(nextSibling, dim);
		DOM.setIntStyleAttribute(prevSibling, dimStyle, prevVal + sz);
		DOM.setIntStyleAttribute(nextSibling, dimStyle, nextVal - sz);
		int newPrevVal = DOM.getIntAttribute(prevSibling, dim);
		int newNextVal = DOM.getIntAttribute(nextSibling, dim);
		if (newPrevVal == prevVal || newNextVal == nextVal) {	// both need to chg
			DOM.setIntStyleAttribute(prevSibling, dimStyle, prevVal);
			DOM.setIntStyleAttribute(nextSibling, dimStyle, nextVal);
		} else
			fixAllSplitterSizes();
	}
	  
	protected void onAttach() {
		super.onAttach();
		allSplitters.add(this);
		Widget prnt = getParent();
		horizOrientation = prnt instanceof Hbox || prnt instanceof HorizontalPanel;
		resetSplitterContainerSize();
		addStyleName(getStyleOrientation());
		updateCollapseView();
	}
	
	private String getStyleOrientation() {
		if (horizOrientation) {
			 return "zk-splitter-horiz";			
		} else
			return "zk-splitter-vert";
	}
	
	protected void onDetach() {
		super.onDetach();
		allSplitters.remove(this);
		destroyCollapseElement(null);
	}
	
	private void updateCollapseView() {
		updateCollapseViewImg();
		updateCollapseViewPosition();
	}
	
	private void updateCollapseViewImg() {
		Element prnt = null;
		try {
			prnt = DOM.getParent(spltElement);	
		} catch (Exception err) {
			GWT.log("gwt-ff-DOMImpl-bug", null);
		}
		if (prnt == null)
			return;
		if (isCollapseNone() && collapseElement == null)
			return;
		if (isCollapseNone()) {
			destroyCollapseElement(prnt);
		} else {
			if (collapseElement == null) 
				createCollapseElement(spltElement);
			updateCollapseImgSrc(false);
			loadCollapseDimIfRequired();
		}
	}
	
	private void loadCollapseDimIfRequired() {
		if (horizOrientation) {
			if (H_COLLAPSE_HEIGHT == -1) 
				H_COLLAPSE_HEIGHT = DOM.getIntAttribute(collapseElement, "offsetHeight");
		} else {
			if (V_COLLAPSE_WIDTH == -1) 
				V_COLLAPSE_WIDTH = DOM.getIntAttribute(collapseElement, "offsetWidth");
		}
	}

	private void updateCollapseImgSrc(boolean reverse) {
		String imgSrcPrefix = "rzk/colps-";
		String imgSrcSuffix = ".gif";
		
		String imgSrc;
		boolean orient = horizOrientation;
		boolean beforeArrow = BEFORE.equals(collapse);
		
		if (reverse)
			beforeArrow = !beforeArrow;
		if (beforeArrow) {
			imgSrc = (orient ? "l": "t");
		} else
			imgSrc = (orient ? "r": "b");
		imgSrc = imgSrcPrefix + imgSrc + imgSrcSuffix;
		DOM.setAttribute(collapseElement, "src", imgSrc);
	}

	private void createCollapseElement(Element splt) {
		collapseElement = DOM.createImg();
		DOM.sinkEvents(collapseElement, Event.ONCLICK);
		DOM.setEventListener(collapseElement, collapseHandler);
		UIObject.setStyleName(collapseElement, "zk-splitter-collapse", true);
		DOM.appendChild(splt, collapseElement);
	}

	private void destroyCollapseElement(Element prnt) {
		if (collapseElement == null)
			return;
		if (prnt == null)
			prnt = DOM.getParent(collapseElement);
		DOM.removeChild(prnt, collapseElement);
		DOM.setEventListener(collapseElement, null);
		collapseElement = null;
		prevSibling = null;
		nextSibling = null;
	}
	
	private void updateCollapseViewPosition() {
		if (collapseElement == null)
			return;
		Element prnt = DOM.getParent(spltElement);
		if (horizOrientation) {
			int height = DOM.getIntAttribute(prnt, "offsetHeight");
			int top = (height - H_COLLAPSE_HEIGHT) / 2;
			DOM.setStyleAttribute(collapseElement, "marginTop", top + "px");
		} else {
			int width = DOM.getIntAttribute(prnt, "offsetWidth");
			int left = (width - V_COLLAPSE_WIDTH) / 2;
			DOM.setStyleAttribute(collapseElement, "marginLeft", left + "px");
		}
	}
	
	private void resetSplitterContainerSize() {
		String style = getStyleOrientation() + "-sz";
		Element prnt = DOM.getParent(spltElement);
		UIObject.setStyleName(prnt, style, false);
		UIObject.setStyleName(prnt, style, true);
	}
	
	private void resetSiblings(Element prnt) {
		if (horizOrientation) {
			prevSibling = ZkUtils.previousSibling(prnt, "TD");
			nextSibling = ZkUtils.nextSibling(prnt, "TD");
		} else {
			Element tr = ZkUtils.parentNode(prnt, "TR");
			Element elem = ZkUtils.previousSibling(tr, "TR");
			prevSibling = DOM.getFirstChild(elem);
			elem = ZkUtils.nextSibling(tr, "TR");
			nextSibling = DOM.getFirstChild(elem);
		}
	}
	
	private void showSibling(boolean show) {
		Element elm = (BEFORE.equals(collapse)) ? prevSibling: nextSibling;
		UIObject.setVisible(elm, show);
		updateCollapseImgSrc(!show);
		resetSplitterContainerSize();
	}
	
	protected class CollapseHandler implements EventListener {
		public void onBrowserEvent(Event event) {
		    switch (DOM.eventGetType(event)) {
		      case Event.ONCLICK: {
	    		  setOpen(!open);
		      }
		    }			
		}
	}
	
	private void handleKeyUpAction(char keyCode, int mods) {
		boolean handle = false;
		int direction = 1;
		switch (keyCode) {
			case KeyboardListener.KEY_LEFT:
			case KeyboardListener.KEY_UP:
				direction = -1;
				handle = true;
				break;
			case KeyboardListener.KEY_RIGHT:
			case KeyboardListener.KEY_DOWN:
				direction = 1;
				handle = true;
				break;
			default:
				if (keyCode == (char)32) 
					setOpen(!open);
				return;
		}
		if (handle) {
			int factor = 0;
			for (int i = 0; i < MOD_KEYS.length; i++) {
				if ((mods & MOD_KEYS[i]) != 0)
					factor += 5;
			}
			factor = Math.max(1, factor);
			resizeSiblings(direction * factor);
		}
	}
	
	static private void fixAllSplitterSizes() {
		for (Iterator iter = allSplitters.iterator(); iter.hasNext();) {
			Splitter s = (Splitter) iter.next();
			s.updateCollapseViewPosition();
		}
	}
	
	static private class WindowResizer extends Timer implements WindowResizeListener {
		public void onWindowResized(int width, int height) {
			this.schedule(120);
		}

		public void run() {
			fixAllSplitterSizes();
		}
	}
}